<?php
declare(strict_types=1);

namespace Zms\PasswordVerifier;

/**
 * 密码验证器
 */
class PasswordVerifier
{

    #大写字母
    const BIG_LETTER = 'big_letter';

    #小写字母
    const SMALL_LETTER = 'small_letter';

    #数字
    const NUMBER = 'number';

    #符号
    const SYMBOL = 'symbol';


    /**
     * 允许的符号内容,不写就是不验证
     * @var array
     */
    public $symbol = ['!', '@', '#', '$', '%', '*', '+', '-', '/', '>', '<'];//符号内容

    /**
     * 验证错误信息
     * @var string|null
     */
    private $error = null;


    /**
     * 密码长度
     * @var int
     */
    private $min_length = 6;


    /**
     * 密码最大长度
     * @var int
     */
    private $max_length = 30;


    /**
     * 是否允许空白字符
     * @var bool
     */
    private $blank = false;

    /**
     * 密码成分
     * @var array
     */
    private $element = [

    ];

    /**
     * @param array{min_length:int,max_length:int,blank:bool,element:array} $config
     * -min_length 最小密码长度
     * -max_length 最大密码长度
     * -blank  密码中是否能含有空白信息
     * -element  密码成分
     */
    public function __construct(array $config = [])
    {
        foreach ($config as $attr => $value) {
            $this->$attr = $value;
        }
    }


    /**
     * 获取错误信息
     * @return string
     */
    public function getError(): string
    {
        return $this->error ?: '';
    }


    /**
     * 验证密码
     * @param string $password
     * @return bool
     */
    public function verifier(string $password): bool
    {
        $pwdlen = mb_strlen($password);
        //验证密码最低长度
        if ($pwdlen < $this->min_length) {
            $this->error = "密码不能少于{$this->min_length}个字符";
            return false;
        }
        //验证密码最大长度
        if ($pwdlen > $this->max_length) {
            $this->error = "密码不能多于{$this->max_length}个字符";
            return false;
        }
        //判断是否含有空白字符
        if ($this->blank === false) {
            if (preg_match('/\s/', $password)) {
                $this->error = "密码中不能含有空白字符";
                return false;
            }
        }
        if (count($this->element) === 0) {
            return true;
        }
        $PwdArray = $this->StrToArray($password);
        //判断是否含有大写字母
        if (in_array(self::BIG_LETTER, $this->element)) {
            $big_list = range('A', 'Z');
            if (!array_intersect($PwdArray, $big_list)) {
                $this->error = "密码中必须包含大写字母";
                return false;
            }
        }


        //判断是否含有小写字母
        if (in_array(self::SMALL_LETTER, $this->element)) {
            $small_list = range('a', 'z');
            if (!array_intersect($PwdArray, $small_list)) {
                $this->error = "密码中必须包含小写字母";
                return false;
            }
        }


        //判断是否含有数字
        if (in_array(self::NUMBER, $this->element)) {
            $num_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
            if (!array_intersect($PwdArray, $num_list)) {
                $this->error = "密码中必须包含数字";
                return false;
            }
        }


        /**
         * 判断是否含有特殊符号
         */
        if (in_array(self::SYMBOL, $this->element) && count($this->symbol) > 0) {
            if (!array_intersect($this->symbol, $PwdArray)) {
                $this->error = "密码中必须包含以下符号:" . implode(',', $this->symbol);
                return false;
            }
        }

        return true;
    }


    /**
     * 密码转数组
     * @param string $string
     * @return array
     */
    private function StrToArray(string $string): array
    {
        $array = [];
        for ($i = 0; $i < mb_strlen($string); $i++) {
            $array[] = mb_substr($string, $i, 1);
        }
        return $array;
    }
}